#!/usr/bin/env python2.6
# -*- encoding: utf-8 -*-
# developed for silix.org (Python 2.4 ):
# License: http://www.gnu.org/licenses/gpl-2.0.txt
#
# Issues:
#  - flag useSharedSms is put to True
#  - in case of Internal Server Error sms aren't send

from poplib import *
import re
import sys
import time
import email
import xml
import base64,quopri

import netfinity
try:
    from m2sconf import *
except ImportError:
    print 'create m2sconf.py'
    sys.exit()

# m2sconf exports:
# SERVER, SSL, USER, PASSWD
# NETPASSWD, DOMAIN

OPTSEP = '___'

#printable = lambda o: ''.join([chr(ord(c)%128) for c in o]) # Eh?
rephonenum = re.compile('3\d{8,9}')

def main():
    server = Server()
    delete = []

    for n,id,msg in server.new_mails_iterator():

        sms = ''
        metasms = '(?noAlias)'
        rereplaces = [] # [ (re pattern,sub), ... ]
        add_sender = True
        add_object = True
        maxlen = 150

        if n==1:
            print time.ctime()
            #print only first time

        user = createSms(msg,onlyuser=True)

        if not user:
            print 'I can\'t find username in email'
            server.delete(n)
            continue

        if OPTSEP in user:
            i = user.index(OPTSEP)
            opts = user[i+len(OPTSEP):]
            user = user[:i]
            phonenum = 'mynumber'
            m2s = 'm2s:'

            #Options
            if 's' in opts:
                metasms += '(?useSharedSms)'
            if 'e' in opts:
                add_sender = False
            if 'o' in opts:
                add_object = False
            if not 'u' in opts:
                # keep URLs
                rereplaces.append(('(http|ftp)://[A-Za-z0-9.~/_-]+','(URL)'))
            if 'x' in opts:
                maxlen = 360
            if '3' in opts[:-8]:
                # phone number
                num = rephonenum.search(opts)
                if num:
                    phonenum = num.group()
            if 'm' in opts:
                m2s = ''

            
        # remove sequences of the same character
        rereplaces.append((re_chr_mul,chr_mul_callback))


        print 'User',user
        client = netfinity.Client(user,NETPASSWD)

        sms += m2s + createSms(msg,rereplaces,add_sender=add_sender,add_object=add_object)[1]

        sms = metasms + sms[:maxlen]

        print 'D:',sms

        try:
            res = int(client.sendMsg(phonenum,sms))
        except xml.sax._exceptions.SAXParseException:
            # SOAP error...
            # I don't know if netfinity sent msg :(
            res = 0
        if res>=0:
            if res>0:
                print 'message sent :)'
            else:
                print "I don't know :|"
            delete.append(n)
        elif res == -2:
            print 'wrong user :('
            delete.append(n)
        elif res == -1:
            print 'Delete mail'
            delete.append(n)
        else:
            print 'netfinity error:',res
    
    #delete notified mails
    for n in delete:
        server.delete(n)
    server.quit()

re_from = re.compile('"(.*?)"')
re_from_addr = re.compile('<(([a-zA-Z0-9_.-]+?)@[a-zA-Z0-9_.-]+?)>')
# group(1) -> address
# group(2) -> name only
re_to_addr = re.compile('Delivered-To: ([a-zA-Z0-9_.-]+)@%s' % DOMAIN)

re_chr_mul = re.compile('((.)\\2{4}\\2*)')
#re_chr_mul = re.compile('((?P<c>.)(?P=c){5}(?P=c)*)')
# group(1) -> cccccc...
def chr_mul_callback(x):
    '''
    remove sequences of the same character.

    if 'cccccccc'>=6:
        'cccccccc' becomes 'c+'
    '''
    g1 = x.group(1)
    return g1[0]+'+'
    #return "%c(x%d)" % (g1[0],len(g1))

printable = lambda o: ''.join([c for c in o if c<=chr(126)])

def get_cte(payload):
    try:
        return [x[1] for x in payload.items() if x[0]=='Content-Transfer-Encoding'][0]
    except IndexError:
        return ''

def msgbody(msg):

    def get_payloads(payload,type):
        # 'text/plain'
        if payload.is_multipart():
            # concatenate result
            r = []
            for i in payload.get_payload():
                r += get_payloads(i,type)
            return r
        elif payload.get_content_type() == type:
            content_enc = get_cte(payload)
            data = payload.get_payload()
            if content_enc=='base64':
                #print 'B64',printable(base64.decodestring(data))
                return [printable(base64.decodestring(data))]
            elif content_enc=='quoted-printable':
                #print 'QP',printable(quopri.decodestring(data))
                return [printable(quopri.decodestring(data))]
            else:
                return [data]
        else:
            return [None] # will be filtered

    pmsg = email.message_from_string('\n'.join(msg))
    
    text_payload = filter(None,get_payloads(pmsg,'text/plain'))
    if text_payload:
        return text_payload[0]

    html_payload = filter(None,get_payloads(pmsg,'text/html'))
        
    if html_payload:
        print 'HTML not supported!'
        #html parsing

    return ''

re_utf8_inline = re.compile('\=\?UTF-8\?([B|Q])\?(.*)\?\=',re.I) #group(1) → B|Q  group(2) → text

def decodeutf8inline(string):

    match = re.match(re_utf8_inline,string)

    if match:
        enct = match.group(1)
        string = match.group(2)
        if enct == 'B':
            # Bsae64
            data = base64.decodestring(string)
            string = printable(str.decode(data,'UTF-8'))
        elif enct == 'Q':
            # Q-encode ~ Homemade :S
            string = string.replace('_',' ')

            re_unquote = re.compile('(=[0-9A-Fa-f]{2})')
            while re.search(re_unquote,string):
                found = re.search(re_unquote,string).group(1)
                string = string.replace(found,chr(int(found[1:3],16)))
            
        return string
    else:
        return string
        

def createSms(msg,rereplaces=[],onlyuser=False,add_sender=True,add_object=True):

    info = {}
    for line in msg:
        if line.startswith('From:'):
            sender = re.search(re_from,line[5:])
            if sender:
                info['sender'] = decodeutf8inline(sender.group(1))
            sender = re.search(re_from_addr,line[5:])
            if sender:
                info['addr sender'] = sender.group(2)
        elif line.startswith('Subject:'):
            info['subject'] = decodeutf8inline(line[8:].strip())

        res = re.search(re_to_addr,line)
        if res and not info.has_key('to'):
            info['to'] = decodeutf8inline(res.group(1))

    if not info.has_key('to'):
        # user not found
        info['to'] = None

    if onlyuser:
        return info['to']

    sms = ''
    if add_sender:
        if info.has_key('sender'):
            print 'D:',info['sender']
            sms += info['sender'] + ':'
        elif info.has_key('addr sender'):
            sms += info['addr sender'] + ':'

    if add_object:
        sms += info.get('subject','') + ':'
    sms += msgbody(msg)

    sms = sms.replace('\n',' ') # remove \n
    sms = re.sub('[ ]+',' ',sms) # remove duplicated spaces

    for s,r in rereplaces:
        sms = re.sub(s,r,sms)

    sms = printable(sms)

    return info['to'],sms.strip().strip(':')

class Server:

    verbose = 1

    def __init__(self):
        if SSL:
            self.s = POP3_SSL(SERVER)
        else:
            self.s = POP3(SERVER)
        assert self.s.getwelcome().startswith('+')
        assert self.s.user(USER).startswith('+')
        assert self.s.pass_(PASSWD).startswith('+')

    def new_mails_iterator(self):
        '''
        yield (num,id,msg)
        '''
        info = self.s.uidl()
        assert info[0].startswith('+')
        nmsg = len(info[1])
        if not nmsg:
            raise StopIteration
        if self.verbose:
            if nmsg==1:
                print 'There is a new message'
            else:
                print 'There are %d new messages' % nmsg
            
        for a in info[1]:
            num,id = a.split()
            num = int(num)
            
            res,msg,octets = self.s.top(num,10000)
            #print octets #seems size of message
            assert res.startswith('+')
            
            yield (num,id,msg)

        raise StopIteration

    def new_mails(self):
        return [x for x in self.new_mails_iterator()]

    def delete(self,num):
        self.s.dele(num)

    def quit(self):
        self.s.quit()

if __name__=="__main__":
    main()
